﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static LightCAD.MathLib.Constants;
using static LightCAD.Three.TypeUtils;
using LightCAD.Three.OpenGL;
using System.Text.RegularExpressions;
using LightCAD.MathLib;

namespace LightCAD.Three
{
    public sealed class CubeUVSize
    {
        public float texelWidth;
        public float texelHeight;
        public int maxMip;
    }

    public class WebGLProgram
    {
        private static int programIdCount = 0;

        public static string handleSource(string _string, int errorLine)
        {

            var lines = _string.split("\n");
            var lines2 = new ListEx<string>();

            var from = Math.Max(errorLine - 6, 0);
            var to = Math.Min(errorLine + 6, lines.Length);

            for (var i = from; i < to; i++)
            {
                var line = i + 1;
                var val = line == errorLine ? ">" : " ";
                lines2.Push($"{val} {line}: ${lines[i]}");
            }

            return lines2.Join("\n");

        }
        public static ListEx<string> getEncodingComponents(int encoding)
        {
            switch (encoding)
            {
                case LinearEncoding:
                    return new ListEx<string> { "Linear", "( value )" };
                case sRGBEncoding:
                    return new ListEx<string> { "sRGB", "( value )" };
                default:
                    console.warn("THREE.WebGLProgram: Unsupported encoding:", encoding);
                    return new ListEx<string> { "Linear", "( value )" };
            }
        }
        public static string getShaderErrors(int shader, string type)
        {

            var status = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
            var errors = gl.getShaderInfoLog(shader).Trim();

            if (status >= 0 && errors == string.Empty) return string.Empty;

            var errorMatches = new Regex(@"ERROR: 0:(\d +)").Matches(errors);
            if (errorMatches != null && errorMatches.Count > 0)
            {

                // --enable-privileged-webgl-extension
                // console.log( "**" + type + "**", gl.getExtension( "WEBGL_debug_shaders" ).getTranslatedShaderSource( shader ) );

                var errorLine = parseInt(errorMatches[1].Value);
                return type.ToUpper() + "\n\n" + errors + "\n\n" + handleSource(gl.getShaderSource(shader), errorLine);

            }
            else
            {

                return errors;

            }

        }

        public static string getTexelEncodingFunction(string functionName, int encoding)
        {

            var components = getEncodingComponents(encoding);
            return "vec4 " + functionName + "( vec4 value ) { return LinearTo" + components[0] + components[1] + "; }";

        }

        public static string getToneMappingFunction(string functionName, int toneMapping)
        {

            string toneMappingName;

            switch (toneMapping)
            {

                case LinearToneMapping:
                    toneMappingName = "Linear";
                    break;

                case ReinhardToneMapping:
                    toneMappingName = "Reinhard";
                    break;

                case CineonToneMapping:
                    toneMappingName = "OptimizedCineon";
                    break;

                case ACESFilmicToneMapping:
                    toneMappingName = "ACESFilmic";
                    break;

                case CustomToneMapping:
                    toneMappingName = "Custom";
                    break;

                default:
                    console.warn("THREE.WebGLProgram: Unsupported toneMapping:", toneMapping);
                    toneMappingName = "Linear";
                    break;
            }

            return "vec3 " + functionName + "( vec3 color ) { return " + toneMappingName + "ToneMapping( color ); }";

        }

        public static string generateExtensions(WebGLPrograms.Parameters parameters)
        {

            var chunks = new ListEx<string> {
                ( parameters.extensionDerivatives!=null ||  parameters.envMapCubeUVHeight!=null || parameters.bumpMap || parameters.normalMapTangentSpace || parameters.clearcoatNormalMap || parameters.flatShading || parameters.shaderID == "physical" ) ? "#extension GL_OES_standard_derivatives : enable" : "",
                (parameters.extensionFragDepth || parameters.logarithmicDepthBuffer) && parameters.rendererExtensionFragDepth ? "#extension GL_EXT_frag_depth : enable" : "",
                (parameters.extensionDrawBuffers && parameters.rendererExtensionDrawBuffers) ? "#extension GL_EXT_draw_buffers : require" : "",
                (parameters.extensionShaderTextureLOD || parameters.envMap || parameters.transmission) && parameters.rendererExtensionShaderTextureLod ? "#extension GL_EXT_shader_texture_lod : enable" : ""
            };

            return chunks.Filter(filterEmptyLine).Join("\n");
        }

        public static string generateDefines(JsObj<object> defines)
        {
            if (defines == null || defines.Count == 0)
                return String.Empty;
            var chunks = new ListEx<string>();
            foreach (var item in defines)
            {
                var name = item.Key;
                var value = defines[name];
                if (value?.ToString().ToLower() == "false") continue;
                chunks.Push("#define " + name + " " + value ?? "");
            }
            return chunks.Join("\n");

        }
        public static JsObj<WebGLAttribute> fetchAttributeLocations(int program)
        {
            var attributes = new JsObj<WebGLAttribute>();

            var n = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);

            for (var i = 0; i < n; i++)
            {

                var info = gl.getActiveAttrib(program, i);
                var name = info.name;

                var locationSize = 1;
                if (info.type == gl.FLOAT_MAT2) locationSize = 2;
                if (info.type == gl.FLOAT_MAT3) locationSize = 3;
                if (info.type == gl.FLOAT_MAT4) locationSize = 4;

                // console.log( "THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:", name, i );

                attributes[name] = new WebGLAttribute
                {
                    type = info.type,
                    location = gl.getAttribLocation(program, name),
                    locationSize = locationSize

                };
            }

            return attributes;

        }

        public static bool filterEmptyLine(string _string)
        {

            return _string != "";

        }

        public static string replaceLightNums(string _string, WebGLPrograms.Parameters parameters)
        {

            var numSpotLightCoords = parameters.numSpotLightShadows + parameters.numSpotLightMaps - parameters.numSpotLightShadowsWithMaps;

            return _string
                .replace("NUM_DIR_LIGHTS", parameters.numDirLights.ToString())
                .replace("NUM_SPOT_LIGHTS", parameters.numSpotLights.ToString())
                .replace("NUM_SPOT_LIGHT_MAPS", parameters.numSpotLightMaps.ToString())
                .replace("NUM_SPOT_LIGHT_COORDS", numSpotLightCoords.ToString())
                .replace("NUM_RECT_AREA_LIGHTS", parameters.numRectAreaLights.ToString())
                .replace("NUM_POINT_LIGHTS", parameters.numPointLights.ToString())
                .replace("NUM_HEMI_LIGHTS", parameters.numHemiLights.ToString())
                .replace("NUM_DIR_LIGHT_SHADOWS", parameters.numDirLightShadows.ToString())
                .replace("NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS", parameters.numSpotLightShadowsWithMaps.ToString())
                .replace("NUM_SPOT_LIGHT_SHADOWS", parameters.numSpotLightShadows.ToString())
                .replace("NUM_POINT_LIGHT_SHADOWS", parameters.numPointLightShadows.ToString());

        }

        public static string replaceClippingPlaneNums(string _string, WebGLPrograms.Parameters parameters)
        {
            return _string
                .replace("NUM_CLIPPING_PLANES", parameters.numClippingPlanes.ToString())
                .replace("UNION_CLIPPING_PLANES", (parameters.numClippingPlanes - parameters.numClipIntersection).ToString());

        }
        // Resolve Includes

        public static readonly string includePattern = @"^[ \t]*#include +<([\w\d./]+)>";

        public static string resolveIncludes(string _string)
        {

            return _string.replace(includePattern, (m) => includeReplacer(m, m.Groups[1].Value), RegexOptions.Multiline);

        }

        public static string includeReplacer(Match match, string include)
        {
            var _string = ShaderChunk.get(include + ".glsl.js");

            if (_string == null)
            {

                throw new Error("Can not resolve #include <" + include + ">");

            }

            return resolveIncludes(_string);

        }

        // Unroll Loops

        public const string unrollLoopPattern = @"#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end";
        public static string unrollLoops(string _string)
        {
            return _string.replace(unrollLoopPattern, (m) => loopReplacer(m, m.Groups[1].Value, m.Groups[2].Value, m.Groups[3].Value));
        }

        public static string loopReplacer(Match match, string start, string end, string snippet)
        {

            var _string = "";

            for (var i = parseInt(start); i < parseInt(end); i++)
            {

                _string += snippet.replace_g(new Regex(@"\[\s*i\s*\]"), "[ " + i + " ]")
                            .replace("UNROLLED_LOOP_INDEX", i.ToString());

            }

            return _string;

        }

        public static string generatePrecision(WebGLPrograms.Parameters parameters)
        {

            var precisionstring = "precision " + parameters.precision + " float;\nprecision " + parameters.precision + " int;";

            if (parameters.precision == "highp")
            {

                precisionstring += "\n#define HIGH_PRECISION";

            }
            else if (parameters.precision == "mediump")
            {

                precisionstring += "\n#define MEDIUM_PRECISION";

            }
            else if (parameters.precision == "lowp")
            {

                precisionstring += "\n#define LOW_PRECISION";

            }

            return precisionstring;

        }

        public static string generateShadowMapTypeDefine(WebGLPrograms.Parameters parameters)
        {

            var shadowMapTypeDefine = "SHADOWMAP_TYPE_BASIC";

            if (parameters.shadowMapType == PCFShadowMap)
            {

                shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF";

            }
            else if (parameters.shadowMapType == PCFSoftShadowMap)
            {

                shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF_SOFT";

            }
            else if (parameters.shadowMapType == VSMShadowMap)
            {

                shadowMapTypeDefine = "SHADOWMAP_TYPE_VSM";

            }

            return shadowMapTypeDefine;

        }

        public static string generateEnvMapTypeDefine(WebGLPrograms.Parameters parameters)
        {

            var envMapTypeDefine = "ENVMAP_TYPE_CUBE";

            if (parameters.envMap)
            {

                switch (parameters.envMapMode)
                {

                    case CubeReflectionMapping:
                    case CubeRefractionMapping:
                        envMapTypeDefine = "ENVMAP_TYPE_CUBE";
                        break;

                    case CubeUVReflectionMapping:
                        envMapTypeDefine = "ENVMAP_TYPE_CUBE_UV";
                        break;

                }

            }

            return envMapTypeDefine;

        }

        public static string generateEnvMapModeDefine(WebGLPrograms.Parameters parameters)
        {

            var envMapModeDefine = "ENVMAP_MODE_REFLECTION";

            if (parameters.envMap)
            {

                switch (parameters.envMapMode)
                {

                    case CubeRefractionMapping:

                        envMapModeDefine = "ENVMAP_MODE_REFRACTION";
                        break;

                }

            }

            return envMapModeDefine;

        }

        public static string generateEnvMapBlendingDefine(WebGLPrograms.Parameters parameters)
        {

            var envMapBlendingDefine = "ENVMAP_BLENDING_NONE";

            if (parameters.envMap)
            {

                switch (parameters.combine)
                {

                    case MultiplyOperation:
                        envMapBlendingDefine = "ENVMAP_BLENDING_MULTIPLY";
                        break;

                    case MixOperation:
                        envMapBlendingDefine = "ENVMAP_BLENDING_MIX";
                        break;

                    case AddOperation:
                        envMapBlendingDefine = "ENVMAP_BLENDING_ADD";
                        break;

                }

            }

            return envMapBlendingDefine;

        }


        public static CubeUVSize generateCubeUVSize(WebGLPrograms.Parameters parameters)
        {

            var imageHeight = parameters.envMapCubeUVHeight;

            if (imageHeight == null) return null;

            var maxMip = Math.Log2(imageHeight.Value) - 2;

            var texelHeight = 1.0f / (float)imageHeight;

            var texelWidth = (float)(1.0 / (3 * Math.Max(Math.Pow(2, maxMip), 7 * 16)));

            return new CubeUVSize { texelWidth = texelWidth, texelHeight = texelHeight, maxMip = (int)maxMip };

        }







        private string name;
        internal int id;
        internal string cacheKey;
        internal int usedTimes;
        private WebGLRenderer renderer;
        private WebGLPrograms.Parameters parameters;
        private WebGLBindingStates bindingStates;
        public readonly JsObj<object> defines;
        public int vertexShader;
        public int fragmentShader;
        public readonly string shadowMapTypeDefine;
        public readonly string envMapTypeDefine;
        public readonly string envMapModeDefine;
        public readonly string envMapBlendingDefine;
        public readonly CubeUVSize envMapCubeUVSize;
        public readonly string customExtensions;
        public readonly string customDefines;
        public int program;
        private string prefixVertex, prefixFragment, versionString;
        private readonly string vertexGlsl, fragmentGlsl;
        private readonly int glVertexShader, glFragmentShader;
        private readonly string programLog, vertexLog, fragmentLog, vertexErrors, fragmentErrors;
        public Diagnostics diagnostics;
        public sealed class DiagnosticsShader
        {
            public string log;
            public string prefix;
        }

        public class Diagnostics
        {
            public bool runnable;

            public string programLog;

            public DiagnosticsShader vertexShader;

            public DiagnosticsShader fragmentShader;
        }


        public WebGLProgram(WebGLRenderer renderer, string cacheKey, WebGLPrograms.Parameters parameters, WebGLBindingStates bindingStates)
        {
            this.renderer = renderer;
            this.cacheKey = cacheKey;
            this.parameters = parameters;
            this.bindingStates = bindingStates;

            this.defines = parameters.defines;

            var vertexShader = parameters.vertexShader;
            var fragmentShader = parameters.fragmentShader;
            this.shadowMapTypeDefine = generateShadowMapTypeDefine(parameters);
            this.envMapTypeDefine = generateEnvMapTypeDefine(parameters);
            this.envMapModeDefine = generateEnvMapModeDefine(parameters);
            this.envMapBlendingDefine = generateEnvMapBlendingDefine(parameters);
            this.envMapCubeUVSize = generateCubeUVSize(parameters);

            this.customExtensions = parameters.isWebGL2 ? "" : generateExtensions(parameters);

            this.customDefines = generateDefines(defines);

            this.program = gl.createProgram();

            this.versionString = !String.IsNullOrEmpty(parameters.glslVersion) ? "#version " + parameters.glslVersion + "\n" : "";

            if (parameters.isRawShaderMaterial)
            {

                prefixVertex = new ListEx<string> {

                        customDefines

                    }.Filter(filterEmptyLine).Join("\n");

                if (prefixVertex.Length > 0)
                {

                    prefixVertex += "\n";

                }

                prefixFragment = new ListEx<string> {

                        customExtensions,
                        customDefines

                    }.Filter(filterEmptyLine).Join("\n");

                if (prefixFragment.Length > 0)
                {

                    prefixFragment += "\n";

                }

            }
            else
            {

                prefixVertex = new ListEx<string> {

                        generatePrecision(parameters),

                        "#define SHADER_NAME " + parameters.shaderName,

                        customDefines,

                        parameters.instancing ? "#define USE_INSTANCING" : "",
                        parameters.instancingColor ? "#define USE_INSTANCING_COLOR" : "",

                        (parameters.useFog && parameters.fog) ? "#define USE_FOG" : "",
                        (parameters.useFog && parameters.fogExp2) ? "#define FOG_EXP2" : "",

                        parameters.map ? "#define USE_MAP" : "",
                        parameters.maps >= 1 ? "#define USE_MAP_ARRAY" : "",//by rider


                        parameters.envMap ? "#define USE_ENVMAP" : "",
                        parameters.envMap ? "#define " + envMapModeDefine : "",
                        parameters.lightMap ? "#define USE_LIGHTMAP" : "",
                        parameters.aoMap ? "#define USE_AOMAP" : "",
                        parameters.bumpMap ? "#define USE_BUMPMAP" : "",
                        parameters.normalMap ? "#define USE_NORMALMAP" : "",
                        parameters.normalMapObjectSpace ? "#define USE_NORMALMAP_OBJECTSPACE" : "",
                        parameters.normalMapTangentSpace ? "#define USE_NORMALMAP_TANGENTSPACE" : "",
                        parameters.displacementMap ? "#define USE_DISPLACEMENTMAP" : "",
                        parameters.emissiveMap ? "#define USE_EMISSIVEMAP" : "",

                        parameters.clearcoatMap ? "#define USE_CLEARCOATMAP" : "",
                        parameters.clearcoatRoughnessMap ? "#define USE_CLEARCOAT_ROUGHNESSMAP" : "",
                        parameters.clearcoatNormalMap ? "#define USE_CLEARCOAT_NORMALMAP" : "",

                        parameters.iridescenceMap ? "#define USE_IRIDESCENCEMAP" : "",
                        parameters.iridescenceThicknessMap ? "#define USE_IRIDESCENCE_THICKNESSMAP" : "",

                        parameters.specularMap ? "#define USE_SPECULARMAP" : "",
                        parameters.specularColorMap ? "#define USE_SPECULAR_COLORMAP" : "",
                        parameters.specularIntensityMap ? "#define USE_SPECULAR_INTENSITYMAP" : "",


                        parameters.roughnessMap ? "#define USE_ROUGHNESSMAP" : "",
                        parameters.metalnessMap ? "#define USE_METALNESSMAP" : "",
                        parameters.alphaMap ? "#define USE_ALPHAMAP" : "",

                        parameters.transmission ? "#define USE_TRANSMISSION" : "",
                        parameters.transmissionMap ? "#define USE_TRANSMISSIONMAP" : "",
                        parameters.thicknessMap ? "#define USE_THICKNESSMAP" : "",

                        parameters.sheenColorMap ? "#define USE_SHEEN_COLORMAP" : "",
                        parameters.sheenRoughnessMap ? "#define USE_SHEEN_ROUGHNESSMAP" : "",

			            //

			            parameters.mapUv!=null ? "#define MAP_UV " + parameters.mapUv : "",
                        parameters.alphaMapUv !=null? "#define ALPHAMAP_UV " + parameters.alphaMapUv : "",
                        parameters.lightMapUv !=null? "#define LIGHTMAP_UV " + parameters.lightMapUv : "",
                        parameters.aoMapUv !=null? "#define AOMAP_UV " + parameters.aoMapUv : "",
                        parameters.emissiveMapUv !=null? "#define EMISSIVEMAP_UV " + parameters.emissiveMapUv : "",
                        parameters.bumpMapUv !=null? "#define BUMPMAP_UV " + parameters.bumpMapUv : "",
                        parameters.normalMapUv !=null? "#define NORMALMAP_UV " + parameters.normalMapUv : "",
                        parameters.displacementMapUv !=null? "#define DISPLACEMENTMAP_UV " + parameters.displacementMapUv : "",

                        parameters.metalnessMapUv!=null ? "#define METALNESSMAP_UV " + parameters.metalnessMapUv : "",
                        parameters.roughnessMapUv!=null ? "#define ROUGHNESSMAP_UV " + parameters.roughnessMapUv : "",

                        parameters.clearcoatMapUv !=null? "#define CLEARCOATMAP_UV " + parameters.clearcoatMapUv : "",
                        parameters.clearcoatNormalMapUv !=null? "#define CLEARCOAT_NORMALMAP_UV " + parameters.clearcoatNormalMapUv : "",
                        parameters.clearcoatRoughnessMapUv !=null? "#define CLEARCOAT_ROUGHNESSMAP_UV " + parameters.clearcoatRoughnessMapUv : "",

                        parameters.iridescenceMapUv !=null? "#define IRIDESCENCEMAP_UV " + parameters.iridescenceMapUv : "",
                        parameters.iridescenceThicknessMapUv !=null? "#define IRIDESCENCE_THICKNESSMAP_UV " + parameters.iridescenceThicknessMapUv : "",

                        parameters.sheenColorMapUv !=null? "#define SHEEN_COLORMAP_UV " + parameters.sheenColorMapUv : "",
                        parameters.sheenRoughnessMapUv !=null? "#define SHEEN_ROUGHNESSMAP_UV " + parameters.sheenRoughnessMapUv : "",

                        parameters.specularMapUv !=null? "#define SPECULARMAP_UV " + parameters.specularMapUv : "",
                        parameters.specularColorMapUv !=null? "#define SPECULAR_COLORMAP_UV " + parameters.specularColorMapUv : "",
                        parameters.specularIntensityMapUv !=null? "#define SPECULAR_INTENSITYMAP_UV " + parameters.specularIntensityMapUv : "",

                        parameters.transmissionMapUv !=null? "#define TRANSMISSIONMAP_UV " + parameters.transmissionMapUv : "",
                        parameters.thicknessMapUv!=null ? "#define THICKNESSMAP_UV " + parameters.thicknessMapUv : "",

			            //

			            parameters.vertexTangents ? "#define USE_TANGENT" : "",
                        parameters.vertexColors ? "#define USE_COLOR" : "",
                        parameters.vertexAlphas ? "#define USE_COLOR_ALPHA" : "",
                        parameters.vertexUvs2 ? "#define USE_UV2" : "",

                        parameters.pointsUvs ? "#define USE_POINTS_UV" : "",


                        parameters.flatShading ? "#define FLAT_SHADED" : "",

                        parameters.skinning ? "#define USE_SKINNING" : "",

                        parameters.morphTargets ? "#define USE_MORPHTARGETS" : "",
                        parameters.morphNormals && parameters.flatShading == false ? "#define USE_MORPHNORMALS" : "",
                        (parameters.morphColors && parameters.isWebGL2) ? "#define USE_MORPHCOLORS" : "",
                        (parameters.morphTargetsCount > 0 && parameters.isWebGL2) ? "#define MORPHTARGETS_TEXTURE" : "",
                        (parameters.morphTargetsCount > 0 && parameters.isWebGL2) ? "#define MORPHTARGETS_TEXTURE_STRIDE " + parameters.morphTextureStride : "",
                        (parameters.morphTargetsCount > 0 && parameters.isWebGL2) ? "#define MORPHTARGETS_COUNT " + parameters.morphTargetsCount : "",
                        parameters.doubleSided ? "#define DOUBLE_SIDED" : "",
                        parameters.flipSided ? "#define FLIP_SIDED" : "",

                        parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "",
                        parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "",

                        parameters.sizeAttenuation ? "#define USE_SIZEATTENUATION" : "",

                        parameters.logarithmicDepthBuffer ? "#define USE_LOGDEPTHBUF" : "",
                        (parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth) ? "#define USE_LOGDEPTHBUF_EXT" : "",


                        "uniform mat4 modelMatrix;",
                        "uniform mat4 modelViewMatrix;",
                        "uniform mat4 projectionMatrix;",
                        "uniform mat4 viewMatrix;",
                        "uniform mat3 normalMatrix;",
                        "uniform vec3 cameraPosition;",
                        "uniform bool isOrthographic;",

                        "#ifdef USE_INSTANCING",

                        "	attribute mat4 instanceMatrix;",

                        "#endif",

                        "#ifdef USE_INSTANCING_COLOR",

                        "	attribute vec3 instanceColor;",

                        "#endif",

                        "#ifdef USE_MAP_ARRAY",//by rider
                        "attribute float mapIndex;",
                        "varying float vMapIndex;",
                        "#endif",



                        "attribute vec3 position;",
                        "attribute vec3 normal;",
                        "attribute vec2 uv;",

                        "#ifdef USE_TANGENT",

                        "	attribute vec4 tangent;",

                        "#endif",

                        "#if defined( USE_COLOR_ALPHA )",

                        "	attribute vec4 color;",

                        "#elif defined( USE_COLOR )",

                        "	attribute vec3 color;",

                        "#endif",

                        "#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )",

                        "	attribute vec3 morphTarget0;",
                        "	attribute vec3 morphTarget1;",
                        "	attribute vec3 morphTarget2;",
                        "	attribute vec3 morphTarget3;",

                        "	#ifdef USE_MORPHNORMALS",

                        "		attribute vec3 morphNormal0;",
                        "		attribute vec3 morphNormal1;",
                        "		attribute vec3 morphNormal2;",
                        "		attribute vec3 morphNormal3;",

                        "	#else",

                        "		attribute vec3 morphTarget4;",
                        "		attribute vec3 morphTarget5;",
                        "		attribute vec3 morphTarget6;",
                        "		attribute vec3 morphTarget7;",

                        "	#endif",

                        "#endif",

                        "#ifdef USE_SKINNING",

                        "	attribute vec4 skinIndex;",
                        "	attribute vec4 skinWeight;",

                        "#endif",

                        "\n"

                    }.Filter(filterEmptyLine).Join("\n");

                prefixFragment = new ListEx<string> {

                        customExtensions,

                        generatePrecision(parameters),

                        "#define SHADER_NAME " + parameters.shaderName,

                        customDefines,

                        parameters.useFog && parameters.fog ? "#define USE_FOG" : "",
                        parameters.useFog && parameters.fogExp2 ? "#define FOG_EXP2" : "",

                        parameters.map ? "#define USE_MAP" : "",
                        parameters.matcap ? "#define USE_MATCAP" : "",
                        parameters.envMap ? "#define USE_ENVMAP" : "",
                        parameters.envMap ? "#define " + envMapTypeDefine : "",
                        parameters.envMap ? "#define " + envMapModeDefine : "",
                        parameters.envMap ? "#define " + envMapBlendingDefine : "",
                        envMapCubeUVSize!=null ? "#define CUBEUV_TEXEL_WIDTH " + envMapCubeUVSize.texelWidth : "",
                        envMapCubeUVSize !=null? "#define CUBEUV_TEXEL_HEIGHT " + envMapCubeUVSize.texelHeight : "",
                        envMapCubeUVSize !=null? "#define CUBEUV_MAX_MIP " + envMapCubeUVSize.maxMip + ".0" : "",
                        parameters.lightMap ? "#define USE_LIGHTMAP" : "",
                        parameters.aoMap ? "#define USE_AOMAP" : "",
                        parameters.bumpMap ? "#define USE_BUMPMAP" : "",
                        parameters.normalMap ? "#define USE_NORMALMAP" : "",
                        parameters.normalMapObjectSpace ? "#define USE_NORMALMAP_OBJECTSPACE" : "",
                        parameters.normalMapTangentSpace ? "#define USE_NORMALMAP_TANGENTSPACE" : "",
                        parameters.emissiveMap ? "#define USE_EMISSIVEMAP" : "",

                        parameters.clearcoat ? "#define USE_CLEARCOAT" : "",
                        parameters.clearcoatMap ? "#define USE_CLEARCOATMAP" : "",
                        parameters.clearcoatRoughnessMap ? "#define USE_CLEARCOAT_ROUGHNESSMAP" : "",
                        parameters.clearcoatNormalMap ? "#define USE_CLEARCOAT_NORMALMAP" : "",

                        parameters.iridescence ? "#define USE_IRIDESCENCE" : "",
                        parameters.iridescenceMap ? "#define USE_IRIDESCENCEMAP" : "",
                        parameters.iridescenceThicknessMap ? "#define USE_IRIDESCENCE_THICKNESSMAP" : "",

                        parameters.specularMap ? "#define USE_SPECULARMAP" : "",
                        parameters.specularColorMap ? "#define USE_SPECULAR_COLORMAP" : "",
                        parameters.specularIntensityMap ? "#define USE_SPECULAR_INTENSITYMAP" : "",

                        parameters.roughnessMap ? "#define USE_ROUGHNESSMAP" : "",
                        parameters.metalnessMap ? "#define USE_METALNESSMAP" : "",

                        parameters.alphaMap ? "#define USE_ALPHAMAP" : "",
                        parameters.alphaTest ? "#define USE_ALPHATEST" : "",

                        parameters.sheen ? "#define USE_SHEEN" : "",
                        parameters.sheenColorMap ? "#define USE_SHEEN_COLORMAP" : "",
                        parameters.sheenRoughnessMap ? "#define USE_SHEEN_ROUGHNESSMAP" : "",

                        parameters.transmission ? "#define USE_TRANSMISSION" : "",
                        parameters.transmissionMap ? "#define USE_TRANSMISSIONMAP" : "",
                        parameters.thicknessMap ? "#define USE_THICKNESSMAP" : "",

                        parameters.decodeVideoTexture ? "#define DECODE_VIDEO_TEXTURE" : "",

                        parameters.vertexTangents ? "#define USE_TANGENT" : "",
                        parameters.vertexColors || parameters.instancingColor ? "#define USE_COLOR" : "",
                        parameters.vertexAlphas ? "#define USE_COLOR_ALPHA" : "",
                        parameters.vertexUvs2 ? "#define USE_UV2" : "",
                        parameters.pointsUvs ? "#define USE_POINTS_UV" : "",

                        parameters.gradientMap ? "#define USE_GRADIENTMAP" : "",

                        parameters.flatShading ? "#define FLAT_SHADED" : "",

                        parameters.doubleSided ? "#define DOUBLE_SIDED" : "",
                        parameters.flipSided ? "#define FLIP_SIDED" : "",

                        parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "",
                        parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "",

                        parameters.premultipliedAlpha ? "#define PREMULTIPLIED_ALPHA" : "",

                        parameters.useLegacyLights ? "#define LEGACY_LIGHTS" : "",

                        parameters.logarithmicDepthBuffer ? "#define USE_LOGDEPTHBUF" : "",
                        (parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth) ? "#define USE_LOGDEPTHBUF_EXT" : "",


                        "uniform mat4 viewMatrix;",
                        "uniform vec3 cameraPosition;",
                        "uniform bool isOrthographic;",


                        (parameters.toneMapping != NoToneMapping) ? "#define TONE_MAPPING" : "",
                        (parameters.toneMapping != NoToneMapping) ? ShaderChunk.get("tonemapping_pars_fragment.glsl.js") : "", // this code is required here because it is used by the toneMapping() function defined below
                        (parameters.toneMapping != NoToneMapping) ? getToneMappingFunction("toneMapping", parameters.toneMapping) : "",

                        parameters.dithering ? "#define DITHERING" : "",
                        parameters.opaque ? "#define OPAQUE" : "",

                        ShaderChunk.get("encodings_pars_fragment.glsl.js"), // this code is required here because it is used by the various encoding/decoding function defined below
                        getTexelEncodingFunction("linearToOutputTexel", parameters.outputEncoding),

                        parameters.useDepthPacking ? "#define DEPTH_PACKING " + parameters.depthPacking : "",

                        "\n"

                    }.Filter(filterEmptyLine).Join("\n");

            }

            vertexShader = resolveIncludes(vertexShader);
            vertexShader = replaceLightNums(vertexShader, parameters);
            vertexShader = replaceClippingPlaneNums(vertexShader, parameters);

            fragmentShader = resolveIncludes(fragmentShader);
            fragmentShader = replaceLightNums(fragmentShader, parameters);
            fragmentShader = replaceClippingPlaneNums(fragmentShader, parameters);

            vertexShader = unrollLoops(vertexShader);
            fragmentShader = unrollLoops(fragmentShader);
            if (parameters.numClippingPlanes > 6)
            {
                fragmentShader = replaceClippingPlaneMultiClipbox(fragmentShader, parameters);
            }

            if (parameters.isWebGL2 && parameters.isRawShaderMaterial != true)
            {

                // GLSL 3.0 conversion for built-in materials and ShaderMaterial

                versionString = "#version 300 es\n";

                prefixVertex = new ListEx<string> {
                        "precision mediump sampler2DArray;",
                        "#define attribute in",
                        "#define varying out",
                        "#define texture2D texture"
                    }.Join("\n") + "\n" + prefixVertex;


                var items = new ListEx<string>();

                prefixFragment = new ListEx<string> {
                    "#define varying in",
                    (parameters.glslVersion == GLSL3) ? "" : "layout(location = 0) out highp vec4 pc_fragColor;",
                    (parameters.glslVersion == GLSL3) ? "" : "#define gl_FragColor pc_fragColor",
                    "#define gl_FragDepthEXT gl_FragDepth",
                    "#define texture2D texture",
                    "#define textureCube texture",
                    "#define texture2DProj textureProj",
                    "#define texture2DLodEXT textureLod",
                    "#define texture2DProjLodEXT textureProjLod",
                    "#define textureCubeLodEXT textureLod",
                    "#define texture2DGradEXT textureGrad",
                    "#define texture2DProjGradEXT textureProjGrad",
                    "#define textureCubeGradEXT textureGrad"
                }.Concat(items).Join("\n") + "\n" + prefixFragment;
            }
            this.vertexGlsl = versionString + prefixVertex + vertexShader;
            this.fragmentGlsl = versionString + prefixFragment + fragmentShader;

            // console.log( "*VERTEX*", vertexGlsl );
            // console.log( "*FRAGMENT*", fragmentGlsl );

            this.glVertexShader = WebGLShaderUtils.WebGLShader(gl.VERTEX_SHADER, vertexGlsl);
            this.glFragmentShader = WebGLShaderUtils.WebGLShader(gl.FRAGMENT_SHADER, fragmentGlsl);

            gl.attachShader(program, glVertexShader);
            gl.attachShader(program, glFragmentShader);

            // Force a particular attribute to index 0.

            if (parameters.index0AttributeName != null)
            {

                gl.bindAttribLocation(program, 0, parameters.index0AttributeName);

            }
            else if (parameters.morphTargets)
            {

                // programs with morphTargets displace position out of attribute 0
                gl.bindAttribLocation(program, 0, "position");

            }

            gl.linkProgram(program);

            // check for link errors
            if (renderer.debug.checkShaderErrors)
            {

                this.programLog = gl.getProgramInfoLog(program).Trim();
                this.vertexLog = gl.getShaderInfoLog(glVertexShader).Trim();
                this.fragmentLog = gl.getShaderInfoLog(glFragmentShader).Trim();

                var runnable = true;
                var haveDiagnostics = true;

                if (gl.getProgramParameter(program, gl.LINK_STATUS) == -1)
                {

                    runnable = false;

                    if (renderer.debug.onShaderError != null)
                    {
                        renderer.debug.onShaderError(program, glVertexShader, glFragmentShader);
                    }
                    else
                    {
                        // default error reporting
                        var vertexErrors = getShaderErrors(glVertexShader, "vertex");
                        var fragmentErrors = getShaderErrors(glFragmentShader, "fragment");

                        console.error(
                            "THREE.WebGLProgram: Shader Error " + gl.getError() + " - " +
                            "VALIDATE_STATUS " + gl.getProgramParameter(program, gl.VALIDATE_STATUS) + "\n\n" +
                            "Program Info Log: " + programLog + "\n" +
                            vertexErrors + "\n" +
                            fragmentErrors
                        );

                    }

                }
                else if (programLog != "")
                {

                    console.warn("THREE.WebGLProgram: Program Info Log:", programLog);

                }
                else if (vertexLog == "" || fragmentLog == "")
                {

                    haveDiagnostics = false;

                }

                if (haveDiagnostics)
                {

                    this.diagnostics = new Diagnostics
                    {

                        runnable = runnable,

                        programLog = programLog,

                        vertexShader = new DiagnosticsShader
                        {

                            log = vertexLog,
                            prefix = prefixVertex


                        },

                        fragmentShader = new DiagnosticsShader
                        {

                            log = fragmentLog,
                            prefix = prefixFragment


                        }

                    };

                }

            }

            // Clean up

            // Crashes in iOS9 and iOS10. #18402
            // gl.detachShader( program, glVertexShader );
            // gl.detachShader( program, glFragmentShader );

            gl.deleteShader(glVertexShader);
            gl.deleteShader(glFragmentShader);
            this.name = parameters.shaderName;
            this.id = programIdCount++;
            this.cacheKey = cacheKey;
            this.usedTimes = 1;
            //this.program = program;
            this.vertexShader = glVertexShader;
            this.fragmentShader = glFragmentShader;
        }
        private WebGLUniforms cachedUniforms;

        public WebGLUniforms getUniforms()
        {

            if (cachedUniforms == null)
            {

                cachedUniforms = new WebGLUniforms(program);

            }

            return cachedUniforms;

        }
        JsObj<WebGLAttribute> cachedAttributes;
        internal JsObj<WebGLAttribute> getAttributes()
        {
            if (cachedAttributes == null)
            {

                cachedAttributes = fetchAttributeLocations(program);

            }

            return cachedAttributes;
        }
        private static string replaceClippingPlaneMultiClipbox(string str, WebGLPrograms.Parameters parameters)
        {
            //#pragma multi_clipbox
            const string code0 = " bool clipped{0}=false;";
            const string code1 = @"plane = clippingPlanes[{1}];
if ( dot( vViewPosition, plane.xyz ) > plane.w )clipped{0}=true;";

            var codes = new ListEx<string>();
            int boxIndex = 0, planeIndex = 0;
            while (planeIndex < parameters.numClippingPlanes)
            {
                boxIndex = (int)Math.Ceiling(planeIndex / 6.0);
                codes.Push(string.Format(code0, boxIndex));
                for (var i = 0; i < 6; i++)
                {
                    var idx = boxIndex * 6 + i;
                    codes.Push(string.Format(code1, boxIndex, idx));
                    planeIndex++;
                    if (planeIndex >= parameters.numClippingPlanes)
                    {
                        break;
                    }
                }
            }
            var boxCount = (int)Math.Ceiling(parameters.numClippingPlanes / 6.0);
            var code2 = "";
            for (var i = 0; i < boxCount; i++)
            {
                if (code2 != "")
                {
                    code2 += " && ";
                }
                code2 += "clipped" + i;
            }
            codes.Push("if(" + code2 + ")discard;");

            str = str.Replace("#pragma multi_clipbox", String.Join("\n", codes));
            return str;
        }
        // free resource
        internal void destroy()
        {
            bindingStates.releaseStatesOfProgram(this);
            gl.deleteProgram(program);
            this.program = 0;
        }
    }
}
